const http = require('http');
const url = require('url');
const fs = require('fs');
const vscode = require('vscode');
const util = require('./util');

const portKey = 'eggHelper.serverPort';
const defaultPort = 35684; // 默认Web服务端口
let errorCount = 0; // 最多错误次数
let lastPort = null;
let lastServer = null;
let myStatusBarItem = null;

/**
 * 创建一个特殊的对象，这个对象会满足：
 * var controller = createSpecialObject('controller');
 * `${controller}` => 输出 ''
 * `${controller.page.home}` => 输出 'page.home'
 * `${controller.api.activity.gmvrank}` => 输出 'api.activity.gmvrank'
 * `${controller.basedata.page.home.index}` => 输出 'basedata.page.home.index'
 * @param {*} prefix 前缀
 */
function createSpecialObject(prefix = '') {
    return new Proxy({}, {
        get(target, key) {
            if (typeof key === 'string') {
                if (key === 'toString') return function(){ return prefix; };
                return createSpecialObject(prefix + (prefix ? '.' : '') + key);
            }
        }
    });
}

/**
 * 创建一个万能的中间件：
 * var middlewares = createSpecialMiddlewares();
 * middlewares.xxx 返回一个空function
 * middlewares.ooo 返回一个空function
 */
function createSpecialMiddlewares() {
    return new Proxy({}, {
        get(target, key) {
            if (typeof key === 'string') {
                return function(){};
            }
        }
    });
}

/**
 * 反向解析Egg路由
 * @param {*} projectPath Egg工程路径
 */
function parseEggRouter(projectPath) {
    const routerPath = `${projectPath}/app/router.js`;
    if (!fs.existsSync(routerPath)) {
        return {code: 1, message: '未找到路由文件：' + routerPath};
    }
    // 模拟一个Egg的app对象
    const app = {
        map: {}, // 存放映射好的结果
        jsonp() {},
        router: {},
        controller: createSpecialObject(),
        middlewares: createSpecialMiddlewares(),
    };
    const methods = ['get', 'post', 'all', 'head', 'options', 'put', 'patch', 'delete', 'del', 'redirect'];
    methods.forEach(method => app.router[method] = app[method] = (...args) => app.map[args[0]] = `${args.pop()}` );
    require(routerPath)(app);
    const routerMap = {};
    // 扫描app下所有的controller文件，放到app.controller对象里面去
    scanFolderSync(`${projectPath}/app/controller`, filePath => {
        if (!/\.js$/g.test(filePath)) return;
        // 形如：page.xxx.customer-info.js
        const router = filePath.replace(`${projectPath}/app/controller/`, '')
            .replace(/\.js$/, '')
            .replace(/\//g, '.')
            .replace(/-(\w)/g, (m, $1) => $1.toUpperCase()); // 中划线转驼峰
        routerMap[router] = filePath;
    });
    const result = {}; // 结果格式：{controller => [absolutePath, selectText]}
    for (const i in app.map) {
        let router = app.map[i];
        // router 形如：page.activity.gmvrank 或者 page.customer.checkInfo.index
        // 所以，可能最后一个单词是文件名，也有可能倒数第二个单词是文件名
        if (routerMap[router]) {
            result[i] = [routerMap[router], null];
        } else {
            // 如果没有找到，可能是因为最后一层是一个function，往前退一层
            const temp = router.split('.');
            const methodName = temp.pop();
            router = temp.join('.');
            if (routerMap[router]) {
                result[i] = [routerMap[router], methodName];
            }
        }
    }
    return {code: 0, data: result};
}

/**
 * 遍历某个文件夹
 */
function scanFolderSync(folderPath, callback, idx) {
	idx = idx == undefined ? 1 : idx;
	if(!fs.statSync(folderPath).isDirectory()) callback(folderPath, idx);
	else {
		fs.readdirSync(folderPath).forEach(function(file) {
			var tempPath = folderPath + '/' + file;  
			if(fs.statSync(tempPath).isDirectory()) scanFolderSync(tempPath, callback, idx+1);
			else callback(tempPath, idx);
		});
	}
}

/**
 * 分析前端传过来的参数
 * @param {*} params [{path, type}]
 */
function analysisParams(params) {
    const errorInfo = [];
    const successInfo = [];
    params.forEach(item => {
        // item.path 形如：ot-hecate/page/register/index.jsx 或者 /ptnBasedata/xxx/xxx.htm
        const projectName = util.toUnderline(item.path.replace(/^\//, '').split('/')[0], '-');
        const router = item.path.replace(/^\/?[^/]+?\//, ''); // 去掉项目名
        const projectPath = util.findProjectPathByName(projectName);
        if (!projectPath) {
            errorInfo.push(`在您名为 ${vscode.workspace.name || '(空)'} 的VSCode窗口中未能找到名为 ${projectName} 的工程，您可能未在本地克隆此项目，或者未将其加入VSCode，如果你打开了多个VSCode窗口，请正确配置各个窗口端口号！`);
            return;
        }
        if (!fs.existsSync(projectPath)) {
            errorInfo.push('未找到目录：' + projectPath + '，您可能本地还没有克隆此项目，或者根目录配置错误。');
            return errorInfo;
        }
        if (item.type === 'js') {
            const filePath = projectPath + '/' + router;
            if (fs.existsSync(filePath)) {
                util.openFileInVscode(filePath, null);
                successInfo.push('成功打开文件：' + filePath);
                return;
            } else {
                errorInfo.push('未找到文件：' + filePath);
                return;
            }
        } else if (item.type === 'page' || item.type === 'api') {
            const { code, message, data } = parseEggRouter(projectPath);
            if (code !== 0) {
                errorInfo.push('解析路由失败：' + message);
                return;
            }
            const [ destPath, methodName ] = data[item.path];
            if (destPath) {
                util.openFileInVscode(destPath, methodName && new RegExp(`(module\\.)?exports\\.${methodName}\\s*=`, ''));
                successInfo.push('成功打开文件：' + destPath);
            } else {
                errorInfo.push('未找到路由对应文件：' + destPath);
            }
        }
    });
    return {errorInfo, successInfo};
}


function start(port) {
    port = port || defaultPort;
    // 如果可能，停止上一个服务
    if (lastServer) {
        try {
            lastServer.close();
        } catch (e) {
            console.error(e);
        }
    }
    // 当前是否是以工作区间的模式打开
    const isWorkspaceMode = !!(vscode.workspace.rootPath && vscode.workspace.name);
    lastServer = http.createServer(function(request, response) {
        let jsonData = '';
        request.on('data', function(chunk) {
            jsonData += chunk;
        });
        request.on('end', function() {
            const params = JSON.parse(jsonData || '[]');
            const pathname = url.parse(request.url).pathname;
            if (pathname == '/openFileByVscode') {
                response.writeHead(200, {'Content-Type': 'application/json;charset=utf-8'});
                try {
                    const {errorInfo, successInfo} = analysisParams(params);
                    if (errorInfo.length) {
                        response.end(JSON.stringify({code: 1, message: errorInfo.join('<br>')}));
                    } else {
                        response.end(JSON.stringify({code: 0, message: successInfo[0]}));
                        util.activeWindow();
                    }
                } catch(e) {
                    response.end(JSON.stringify({code: 0, message: `打开VSCode失败：${e.message}`}));
                }
                
            } else {
                response.writeHead(200, {'Content-Type': 'text/plain;chartset=utf-8'});
                response.end('Welcome to egg helper web server.');
            }
        });
    })
    .listen(port)
    .on('error', e => {
        console.error(e);
        errorCount++;
        // 最多重试20次，防止无限循环
        if (errorCount < 20) {
            // 如果是工作区间模式打开的，端口往上自增，否则往下自增，防止已经保存的端口被修改
            const random = Math.floor(Math.random() * 29000); // 随机生成一个0-29000的数字
            port = isWorkspaceMode ? defaultPort + random : defaultPort - random;
            start(port);
        } else {
            const message = e.code === 'EADDRINUSE' ? `端口冲突(${port})` : 'Web服务启动失败';
            util.showError(`Egg助手${message}，请到设置页手动修改端口。`);
            updateStatusBarItem(message);
            lastServer = null;
        }
    })
    .on('listening', () => {
        // 终端打印如下信息
        console.log(`Egg Helper Server running at http://127.0.0.1:${port}/`);
        updateStatusBarItem(port);
        // 如果当前打开的是工作区，写入工作区配置，否则不保存
        if (isWorkspaceMode) {
            util.setConfig(portKey, port, false);
        }
        console.log(vscode.workspace);
    });
}

/**
 * 更新状态栏
 * @param {*} text 
 */
function updateStatusBarItem(text) {
    if (!myStatusBarItem) return;
    if (text) {
        myStatusBarItem.text = `Egg助手：${text}`;
		myStatusBarItem.show();
    } else {
        myStatusBarItem.hide();
    }
}

module.exports = function(context) {
    myStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 0);
    context.subscriptions.push(myStatusBarItem);
    lastPort = util.getConfig(portKey);
    start(lastPort);
    context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => {
        // 任何配置变化都会触发这个方法，所以这里通过lastPort来对比是否发生变化
        const port = util.getConfig(portKey);
        if (lastPort !== port) {
            lastPort = port;
            start(lastPort);
        }
    }));
};